#include "magicbrushtool.h"

MagicBrushTool::MagicBrushTool()
{
    global = GlobalStates::getInstance();
    varToCheck = 0;
    redTolerance = 0.0;
    greenTolerance = 0.0;
    blueTolerance = 0.0;
    isContiguous = true;
    action = NEW;
    toggleVariable(COLOR);


}


MagicBrushTool * MagicBrushTool::getInstance(){
    static MagicBrushTool* inst=0;
    if(!inst)inst = new MagicBrushTool();
    inst->global = GlobalStates::getInstance();
    return inst;
}


string MagicBrushTool::getType() {
    return "magicbrush";
}


Result MagicBrushTool::setAction(ACTION a){
    action = a;
    string msg = "You are now ";
    if (a==ADD) msg = msg.append("adding to the selection");
    else if (a==NEW) msg = msg.append("making a new selection");
    else msg = msg.append("subtracting from the selection");
    return Result(true, msg);
}


float MagicBrushTool::getColourTolerance(FIX_COLOUR c){
    if (c==RED){
        return redTolerance;
    }
    else if (c==GREEN){
        return greenTolerance;
    }
    else
        return blueTolerance;
}

bool MagicBrushTool::isVariableSet(VARIABLE var){
    return varToCheck & var;
}

void MagicBrushTool::toggleVariable(VARIABLE var){
    if (isVariableSet(var)) {
        varToCheck &= ~var;
    }
    else {
        varToCheck |= var;
    }
}

Result MagicBrushTool::setColourTolerance(FIX_COLOUR c, float tol){
    if (tol>=0 && tol <=100){
        if (c == RED){
            redTolerance = tol;
            return Result(true, "Red Tolerance set");
        }
        else if (c==BLUE){
            blueTolerance = tol;
            return Result(true, "Blue tolerance set");
        }
        else {
            greenTolerance = tol;
            return Result(true, "Green tolerance set");
        }
    }

    else return Result(false, "Invalid tolerance value");
}

void MagicBrushTool::toggleContiguous(bool b){
    isContiguous = b;
}

int MagicBrushTool::getAction(){
    return action;
}

bool MagicBrushTool::checkVoxelProperties(vector3<int> v, colour origColor, bool inSelection, set<vector3<int> >& permitted){
    if (REND->isVoxelDrawn(v.x, v.y, v.z)){
        if ((inSelection && permitted.find(v)!=permitted.end()) || !inSelection) {
            if(isVariableSet(COLOR)){
                colour thisColor = REND->getVoxelColor(v.x,v.y,v.z);
                float lowerThreshold = (100 - redTolerance)/100;
                float upperThreshold = (100 + redTolerance)/100;
                if (thisColor.r < lowerThreshold*origColor.r || thisColor.r > upperThreshold*origColor.r ) {
                    return false;
                }
                lowerThreshold = (100 - greenTolerance)/100;
                upperThreshold = (100 - greenTolerance)/100;
                if (thisColor.g < lowerThreshold*origColor.g || thisColor.g > upperThreshold*origColor.g) {
                    return false;
                }

                lowerThreshold = (100 - blueTolerance)/100;
                upperThreshold = (100 - blueTolerance)/100;
                if (thisColor.b < lowerThreshold*origColor.b || thisColor.b > upperThreshold*origColor.b) {
                    return false;
                }
                return true;
            }
        }
        return false;
    }
    return false;
}

Result MagicBrushTool::execute(){

    Result r(true, "No action yet");
    bool inSelection = false;
    set<vector3<int> > permittedSet;

    if(action==NEW || action == SUBTRACT) { //two cases: either magic brush falls inside selection or it doesn't and it clears.
        inSelection = global->findInSelection(coords.x, coords.y, coords.z);
        if (inSelection) {
            permittedSet = global->getSelection();
        }
        if (action == NEW) {
            global->clearSelection();
        }
    }

    colour c = REND->getVoxelColor(coords.x, coords.y, coords.z);

    if (isContiguous) {
        set<vector3<int> >traversed;
        queue<vector3<int> > q;

        q.push(coords);
        traversed.insert(coords);

        while (!q.empty()){
            vector3<int> current = q.front();
            vector3<int> left(current.x-1, current.y, current.z);
            vector3<int> right(current.x+1, current.y, current.z);
            vector3<int> up(current.x, current.y+1, current.z);
            vector3<int> down(current.x, current.y-1, current.z);
            vector3<int> front(current.x, current.y, current.z-1);
            vector3<int> back(current.x, current.y, current.z+1);

            if (traversed.find(left)==traversed.end() && checkVoxelProperties(left,c, inSelection, permittedSet)){
                q.push(left);
                traversed.insert(left);
            }
            if (traversed.find(right)==traversed.end() && checkVoxelProperties(right,c, inSelection, permittedSet)){
                q.push(right);
                traversed.insert(right);
            }
            if (traversed.find(up)==traversed.end() && checkVoxelProperties(up,c, inSelection, permittedSet)){
                q.push(up);
                traversed.insert(up);
            }
            if (traversed.find(down)==traversed.end() && checkVoxelProperties(down,c, inSelection, permittedSet)){
                q.push(down);
                traversed.insert(down);
            }
            if (traversed.find(front)==traversed.end() &&checkVoxelProperties(front,c, inSelection, permittedSet)){
                q.push(front);
                traversed.insert(front);
            }
            if (traversed.find(back)==traversed.end() && checkVoxelProperties(back,c, inSelection, permittedSet)){
                q.push(back);
                traversed.insert(back);
            }


            if (action==NEW || action==ADD) {
                r.andRes(global->addVoxelToSelection(current.x, current.y, current.z));
            }
            else if (action == SUBTRACT){
                r.andRes(global->deleteVoxelFromSelection(current.x, current.y, current.z));
            }
            q.pop();
        }

    }
    else {
        for (int i = 0; i<WORLD_XSIZE; i++)
            for (int j = 0; j<WORLD_YSIZE; j++)
                for (int k = 0; k<WORLD_ZSIZE; k++){
                    vector3<int>current(i, j, k);
                    if (checkVoxelProperties(current,c, inSelection, permittedSet)) {
                        if (action==NEW || action==ADD) {
                            r.andRes(global->addVoxelToSelection(current.x, current.y, current.z));
                        }
                        else if (action == SUBTRACT){
                            r.andRes(global->deleteVoxelFromSelection(current.x, current.y, current.z));
                        }


                    }
                }
    }
    return r;
}



Result MagicBrushTool::keyPressEvent(QKeyEvent *event){
    int key = event->key();
    switch(key){
    case (KEY_RESET):
        global->clearSelection();
        return Result(true, "Selection cleared");
    }
    return Result(false, "No recognised key pressed");
}

Result MagicBrushTool::mousePressEvent(QMouseEvent *event){
    Result r(false, "No recognised mouse button");
    if (event->button()==Qt::LeftButton) {
        r.setMessage("Left button pressed");
        vector3<int> mouseCoords = REND->mouseUtils.getMouseOverVoxelPt(event->x(), event->y());
        if (REND->isVoxelDrawn(mouseCoords.x, mouseCoords.y, mouseCoords.z)) {
            coords = mouseCoords;
            if (event->modifiers() & KEY_ADD) {
                action = ADD;
            }
            else if (event->modifiers() & KEY_SUBTRACT) {
                action = SUBTRACT;
            }
            else {
                action = NEW;
            }
            r = execute();
        }
    }
    return r;
}

bool MagicBrushTool::checkContiguity() {
    return isContiguous;
}
